package com.kay.factory;

import com.kay.annote.Autowired;
import com.kay.annote.Service;
import com.kay.annote.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.reflections.Reflections;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Description: 创建对象的工厂类
 * 1. 解析bean.xml/特定包下@Servicw注解 将读取出来的对象相信存放到BeanFactory中(Map集合中), 生成代理对象
 * 2. 对外提供获取对象的接口
 * @Author: Kay
 * @CreateDate: 2021/7/3$ 11:11$
 * @UpdateUser: Kay
 * @UpdateDate: 2021/7/3$ 11:11$
 */
public class BeanFactory {
    private static Map<String, Object> beanMap = new HashMap<>(); // key: beanId    ;  value: 类实例
    private static Map<String, String> classNameBindBeanIdMap = new HashMap<>(); //key:全限定类名 value: beanId

    /**
     * 读取bean.xml中的标签，解析成对应对象存放到 beanMap 集合中
     */
    static {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml"));
            Element rootElement = document.getRootElement();
            // 要扫描的包路径
            List<Element> scanPackageList = rootElement.selectNodes("//component-scan");
            // 1. 扫描 特定包下 @Service 注解 创建实例对象存放在 beanMap中
            scanServiceAnnPutInBeanMap(scanPackageList);

            // 2. 处理XML中 bean 标签 创建实例对象存放在 beanMap 中 // todo 未处理 beanId 重复问题
            parseBeanPutInBeanMap(rootElement);

            // 3. 处理beanMap中 @Autowired 注解，给对象注入依赖 // todo 没有考虑属性注解了@Autowired但是类没有使用@Service情况
            autowiredIC();

            //4. 处理XML属性标签 <property> 为需要配置的对象注入对象（即 TransferServiceImpl对象中，需要注入 AccountDao ）
            parsePropertyIC(rootElement);

            // 5. @Transactional 注解类对象生成代理对象
            transactionalAddProxy(scanPackageList);

        } catch (DocumentException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描 特定包下 @Service 注解 创建实例对象存放在 beanMap中
     *
     * @param scanPackageList
     */
    private static void scanServiceAnnPutInBeanMap(List<Element> scanPackageList) throws IllegalAccessException, InstantiationException {
        if (scanPackageList != null) {
            for (Element element : scanPackageList) {
                String packageName = element.attributeValue("class");
                Reflections reflections = new Reflections(packageName);
                // 扫描 特定包下 @Service 注解 --------------
                Set<Class<?>> serviceSet = reflections.getTypesAnnotatedWith(Service.class);
                if (serviceSet != null) {
                    for (Class<?> aClass : serviceSet) {
                        // 获取注解了 @Service 的类的实例
                        Service annotation = aClass.getAnnotation(Service.class);
                        String beanId = annotation.value();
                        String classPathName = aClass.getName();
                        String[] split = classPathName.split("\\.");
                        String className = split[split.length - 1];
                        // beanId 默认首字母小写，  Service中配置了value值，以value为准
                        beanId = StringUtils.isBlank(beanId) ? className.substring(0, 1).toLowerCase() + className.substring(1) : beanId;
                        beanMap.put(beanId, aClass.newInstance());
                        classNameBindBeanIdMap.put(classPathName, beanId);
                    }
                }
            }
        }
    }

    /**
     * 处理XML中 bean 标签 创建实例对象存放在 beanMap 中
     *
     * @param rootElement
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private static void parseBeanPutInBeanMap(Element rootElement) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        List<Element> beanList = rootElement.selectNodes("//bean");
        if (beanList != null) {
            for (Element element : beanList) {
                // 获取 <bean> 标签中对应的 id 及 实现类类路径
                String id = element.attributeValue("id"); // transferService
                String classPath = element.attributeValue("class"); //com.kay.service.impl.TransferServiceImpl

                // 反射实例化类对象
                Object object = Class.forName(classPath).newInstance();
                // 将对象存放到map集合中
                beanMap.put(id, object);
                classNameBindBeanIdMap.put(classPath, id);
            }
        }
    }

    /**
     * 处理beanMap中 @Autowired 注解，给对象注入依赖
     *
     * @throws IllegalAccessException
     */
    private static void autowiredIC() throws IllegalAccessException {
        Reflections reflections = new Reflections();
        for (Map.Entry<String, Object> map : beanMap.entrySet()) {
            Object beanObject = map.getValue();
            Class<?> beanObjectClass = beanObject.getClass();
            Field[] fields = beanObjectClass.getDeclaredFields(); // 获取类对应的所有属性
            for (Field field : fields) {
                boolean autowiredPresent = field.isAnnotationPresent(Autowired.class);
                if (autowiredPresent) {

                    Class<?> aClass = field.getType();
                    if (aClass.isInterface()) {  // 判断如果时接口，获取其实现类
                        Set<Class<?>> subTypesOf = reflections.getSubTypesOf((Class<Object>) aClass);

                        if (subTypesOf.size() > 1) throw new RuntimeException(aClass.getName() + "存在多个实现类!");

                        aClass = subTypesOf.iterator().next();
                    }
                    String classPathName = aClass.getName();
                    field.setAccessible(true);
                    field.set(beanObject, beanMap.get(classNameBindBeanIdMap.get(classPathName))); // 暴力反射 直接赋值 去除set方法
                }
            }
            beanMap.put(map.getKey(), beanObject);
        }
    }

    private static void parsePropertyIC(Element rootElement) throws InvocationTargetException, IllegalAccessException {
        List<Element> propertyList = rootElement.selectNodes("//property");
        if (propertyList != null) {
            for (Element element : propertyList) {
                // 通过set+Id注入对象里面的引用
                String methodName = "set" + element.attributeValue("name"); //set +  AccountDao
                String ref = element.attributeValue("ref"); // accountDao

                Object obj = beanMap.get(ref); //需要被注入的对象  AccountDao
                // 获取当前element的父级节点
                Element parentElement = element.getParent();
                String parentId = parentElement.attributeValue("id"); // transferService
                Object parentObject = beanMap.get(parentId);

                // 获取父节点所有方法，符合 methodName 的注入对应的属性对象
                Method[] methods = parentObject.getClass().getMethods();
                for (Method method : methods) {
                    if (method.getName().equals(methodName)) { // setAccountDao
                        // 调用该方法，将 需要被注入的对象（AccountDao）传入
                        method.invoke(parentObject, obj);
                    }
                }
                // parentObject被重新赋值了，需要覆盖beanMap中原值
                beanMap.put(parentId, parentObject);
            }
        }
    }

    private static void transactionalAddProxy(List<Element> scanPackageList) {
        ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
        if (scanPackageList != null) {
            for (Element element : scanPackageList) {
                String packageName = element.attributeValue("class");
                Reflections reflections = new Reflections(packageName);
                Set<Class<?>> transactionalSet = reflections.getTypesAnnotatedWith(Transactional.class);
                if (transactionalSet != null) {
                    for (Class<?> aClass : transactionalSet) {
                        String beanId = classNameBindBeanIdMap.get(aClass.getName()); // 通过类路径获取beanId
                        Object object = beanMap.get(beanId); // 获取实例化后的对象
                        // 生成代理对象
                        object = proxyFactory.getProxyObject(object);
                        // 生成对象的 代理对象存放在 beanMap中
                        beanMap.put(beanId, object);
                    }
                }
            }
        }
    }

    /**
     * 根据传入的类ID 返回实体类
     *
     * @param id
     * @return
     */
    public static Object getBean(String id) {
        return beanMap.get(id);
    }
}
